/*
LoveWaves Project
Acceliis T4W RFID reader - T4W library wrapper (JNI implementation)
Copyright (C) 2006, Eric Sigoillot

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
#include <jni.h>
#include <windows.h>
#include <stdlib.h>
#include "lovewaves_T4Reader.h"

/* T4 library functions definitions */
typedef LPCTSTR (__stdcall *T4FindCOMPortFunc)(void);
typedef int (__stdcall *T4InitFunc)(LPCTSTR);
typedef int (__stdcall *T4DeInitFunc)(void);
typedef int (__stdcall *T4SelectTagFunc)(BYTE *, BYTE *);

/* Default tag type size */
#define TAG_TYPE_SIZE	1
/* Default serial number (UID) size */
#define TAG_ID_SIZE		8

/* T4W library instance */
HINSTANCE t4wInst = NULL;

/* Loads the T4W library */
JNIEXPORT jboolean JNICALL Java_lovewaves_T4Reader_T4LoadLibrary(JNIEnv *env, jclass c) {
	t4wInst = LoadLibrary("T4W.dll");
	
	if (t4wInst) {
		return JNI_TRUE;
	} else {
		return JNI_FALSE;	
	}
}

/* Frees the T4W library */
JNIEXPORT void JNICALL Java_lovewaves_T4Reader_T4FreeLibrary(JNIEnv *env, jclass c) {
	if (t4wInst) {
		FreeLibrary(t4wInst);
	}
}

/* Finds on which COM port a T4 reader might be attached and returns its name */
JNIEXPORT jstring JNICALL Java_lovewaves_T4Reader_T4FindCOMPort(JNIEnv *env, jclass c) {
	if (t4wInst) {
		/* Loads the library function */
		T4FindCOMPortFunc T4FindCOMPort = (T4FindCOMPortFunc)GetProcAddress(t4wInst, "_T4FindCOMPort@0");
		if (T4FindCOMPort) {
			/* Calls the library */
			LPCTSTR result = T4FindCOMPort();
			/* Encapsulates the result into a Java string */
			return (*env)->NewStringUTF(env, result);
		} else {
			printf("Error");
		}
	}

	return NULL;
}

/* Initializes the T4 reader and returns the error code (0 means success) */
JNIEXPORT jint JNICALL Java_lovewaves_T4Reader_T4Init(JNIEnv *env, jclass c, jstring port) {
	if (t4wInst) {
		/* Loads the library function */
		T4InitFunc T4Init = (T4InitFunc)GetProcAddress(t4wInst, "_T4Init@4");
		if (T4Init) {
			/* Converts the Java string into a char* buffer */
			const char *cport = (*env)->GetStringUTFChars(env, port, 0);
			/* Initializes reader on given COM port */
			int result = T4Init(cport);
			return result;
		}
	}

	return -1;
}

/* Terminates the T4 reader and returns the error code (0 means success) */
JNIEXPORT jint JNICALL Java_lovewaves_T4Reader_T4DeInit(JNIEnv *env, jclass c) {
	if (t4wInst) {
		/* Loads the library function */
		T4DeInitFunc T4DeInit = (T4DeInitFunc)GetProcAddress(t4wInst, "_T4DeInit@0");
		if (T4DeInit) {
			/* Terminates the reader */
			int result = T4DeInit();
			return result;
		}
	}

	return -1;
}

/* Reverses a byte array ( [0 1 2 3] gives [3 2 1 0]) */
BYTE *ReverseArray(BYTE *array, size_t len)
{
    size_t i;
    BYTE swap;
    
    for(i = 0; i < --len; i++) {
        swap = array[i];
        array[i] = array[len];
        array[len] = swap;
    }
    
    return array;
}

/* Waits for a tag to be selected by the T4 reader and returns the tag information 
   CAUTION! This function should only be used in a dedicated thread
*/
JNIEXPORT jobject JNICALL Java_lovewaves_T4Reader_T4SelectTag(JNIEnv *env, jclass c) {
	if (t4wInst) {
		/* Loads the library function */
		T4SelectTagFunc T4SelectTag = (T4SelectTagFunc)GetProcAddress(t4wInst, "_T4SelectTag@8");
		if (T4SelectTag) {
			/* Allocates buffers for the tag type and serial number (UID) */
			BYTE *ctagType = calloc(TAG_TYPE_SIZE, 1);
			BYTE *cserialNumber = calloc(TAG_ID_SIZE, 1);
			jobject obj = 0;
			jbyteArray jarray;
			int result = -1;
			
			/* Waits for a tag selection */
			while (result != 0) {
				result = T4SelectTag(ctagType, cserialNumber);
			}
			
			/* Queries for a TagInfo Java class */
    		c = (*env)->FindClass(env, "lovewaves/TagInfo");
    		if (c != 0) {
				/* Builds a TagInfo class, without using the constructor */
		    	obj = (*env)->AllocObject(env, c);
		    	
		    	/* Tag type */
		    	jfieldID tagTypeID = (*env)->GetFieldID(env, c, "tagType", "[B");
    			if (tagTypeID != 0) {
					/* Creates a Java array holding the tag type */
					jarray = (*env)->NewByteArray(env, TAG_TYPE_SIZE);
					/* Copies the C array to the Java array */
    				(*env)->SetByteArrayRegion(env, jarray, 0, TAG_TYPE_SIZE, ReverseArray(ctagType, 8));
					/* Sets the TagInfo instance property with the Java array */
					(*env)->SetObjectField(env, obj, tagTypeID, jarray);
				}
				
				/* Serial Number */
				jfieldID serialNumberID = (*env)->GetFieldID(env, c, "serialNumber", "[B");
				if (serialNumberID != 0) {
					/* Creates a Java array holding the tag serial number */
		    		jarray = (*env)->NewByteArray(env, TAG_ID_SIZE);
					/* Copies the C array to the Java array */
		    		(*env)->SetByteArrayRegion(env, jarray, 0, TAG_ID_SIZE, ReverseArray(cserialNumber, 8));
					/* Sets the TagInfo instance property with the Java array */
					(*env)->SetObjectField(env, obj, serialNumberID, jarray);
				}
			}		
			
			/* Frees buffers memory */
			free(ctagType);
			free(cserialNumber);
			
			return obj;
		}
	}

	return (jobject)0;
}

/* DLL entry point */
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch (fdwReason)
    {
        case DLL_PROCESS_ATTACH:
            // attach to process
            // return FALSE to fail DLL load
            break;

        case DLL_PROCESS_DETACH:
            // detach from process
            break;

        case DLL_THREAD_ATTACH:
            // attach to thread
            break;

        case DLL_THREAD_DETACH:
            // detach from thread
            break;
    }
    return TRUE; // succesful
}
